'use strict';
import * as THREE from 'three';
import {
  EventDispatcher,
  Mesh,
  PlaneBufferGeometry,
  Raycaster,
  Vector2,
} from 'three';
import DrawController from './controller/DrawController';
import DebugInfo from './DebugInfo';
import ViewManager from './ViewManager';

export enum State {
  None,
  CameraMove,
}

export const WinEvent = {
  onmousemove: 'onmousemove',
  onmousewheel: 'onmousewheel',
  onmousedown: 'onmousedown',
  onmouseup: 'onmouseup',
  onkeydown: 'onkeydown',
  onkeyup: 'onkeyup',
};

class ThreeApp extends EventDispatcher {
  public scene: THREE.Scene;
  public camera: THREE.OrthographicCamera;
  public renderer: THREE.WebGLRenderer;
  public plane: Mesh;
  public rayCast: Raycaster;
  public state: State = null;

  private element: HTMLCanvasElement = null;
  private isRunning: boolean = false;
  private viewHalfWidth: number = 500;
  private zoom: number = 1.0;
  private zoomUnit: HTMLElement = null;
  private zoomUnitText: HTMLElement = null;
  private followLabel: HTMLElement = null;

  private _viewManager: ViewManager;
  private _drawController: DrawController;

  public get drawController(): DrawController {
    return this._drawController;
  }

  public get viewManager(): ViewManager {
    return this._viewManager;
  }
  public getElement() {
    return this.element;
  }

  public get clientWidth() {
    return this.element ? this.element.clientWidth : 0;
  }

  public get clientHeight() {
    return this.element ? this.element.clientHeight : 0;
  }

  public create() {
    this.destory();
    const canvars = document.createElement('canvas');
    this.element = canvars;

    window.onresize = this.onresize.bind(this);
    window.onmousewheel = (e) => {
      this.onmousewheel(e as WheelEvent);
    };
    this.element.onmousemove = (e) => {
      this.onmousemove(e as MouseEvent);
    };
    this.element.onmouseenter = () => {
      if (this.followLabel) {
        this.followLabel.style.display = 'block';
      }
    };
    this.element.onmouseleave = () => {
      if (this.followLabel) {
        this.followLabel.style.display = 'none';
      }
    };

    window.onkeydown = (e: KeyboardEvent) => {
      this.onkeydown(e);
    };

    window.onkeyup = (e: KeyboardEvent) => {
      this.onkeyup(e);
    };

    this.element.onmousedown = (e) => {
      this.onmousedown(e as MouseEvent);
    };
    this.element.onmouseup = (e) => {
      this.onmouseup(e as MouseEvent);
    };
    this.element.oncontextmenu = (e) => {
      e.preventDefault();
    };
    return canvars;
  }

  public start() {
    //创建场景.
    this.scene = new THREE.Scene();
    //相机
    const aspect = this.clientHeight / this.clientWidth;
    this.camera = new THREE.OrthographicCamera(
      -this.viewHalfWidth,
      this.viewHalfWidth,
      this.viewHalfWidth * aspect,
      -this.viewHalfWidth * aspect,
      -100,
      100
    );

    //渲染器
    this.renderer = new THREE.WebGLRenderer({
      canvas: this.element,
    });
    //设置画布大小
    // this.renderer.setSize(this.element.clientWidth, this.element.clientHeight);
    this.renderer.setDrawingBufferSize(
      this.element.clientWidth,
      this.element.clientHeight,
      1
    );
    this.renderer.setClearColor(0xf0f4f0, 1.0);
    this.renderer.autoClear = false;

    let geometry = new THREE.Geometry();
    geometry.vertices.push(
      new THREE.Vector3(-2e6, 0, 0),
      new THREE.Vector3(2e6, 0, 0),
      new THREE.Vector3(0, 2e6, 0),
      new THREE.Vector3(0, -2e6, 0)
    );
    let material = new THREE.MeshBasicMaterial({
      color: 0xb0a9b0,
    });
    let axis = new THREE.LineSegments(geometry, material);
    this.scene.add(axis);

    this.plane = new Mesh(new PlaneBufferGeometry(4e6, 4e6), material);
    this.plane.visible = false;
    // this.scene.add(this.plane);
    this.rayCast = new Raycaster();

    let geometryUnit = new THREE.Geometry();
    geometryUnit.vertices.push(
      new THREE.Vector3(0, 0, 99),
      new THREE.Vector3(1, 0, 99)
    );

    this.zoomUnit = document.createElement('div');
    document.body.appendChild(this.zoomUnit);
    this.zoomUnit.style.position = 'absolute';
    this.zoomUnit.style.zIndex = '100';
    this.zoomUnit.style.top = '30';
    this.zoomUnit.style.right = '50';
    this.zoomUnit.style.width = '40';
    this.zoomUnit.style.borderTopStyle = 'solid';
    this.zoomUnit.style.borderTopWidth = '1px';
    this.zoomUnit.style.borderTopColor = '#ff0000';

    this.zoomUnitText = document.createElement('div');
    document.body.appendChild(this.zoomUnitText);
    this.zoomUnitText.style.position = 'absolute';
    this.zoomUnitText.style.zIndex = '100';
    this.zoomUnitText.style.top = '35';
    this.zoomUnitText.style.right = '50';
    const unitScale = (this.viewHalfWidth / this.clientWidth) * 2; // 每个像素宽
    this.zoomUnitText.innerText = (unitScale * 40).toPrecision(5).toString();

    this.followLabel = document.createElement('div');
    document.body.appendChild(this.followLabel);
    const flStyle = this.followLabel.style;
    flStyle.display = 'block';
    flStyle.position = 'absolute';
    flStyle.zIndex = '99';
    flStyle.top = '-1000';
    flStyle.left = '-1000';
    this.followLabel.innerText = 'follow Label.';

    this._viewManager = new ViewManager();
    this._drawController = new DrawController();

    // let geometry2 = new THREE.BoxGeometry(201, 221, 221);
    // let material2 = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
    // let cube = new THREE.Mesh(geometry2, material2);
    // this.scene.add(cube);

    //设置相机位置
    this.camera.position.z = 100;
    //渲染循环
    const animate = () => {
      requestAnimationFrame(animate);
      DebugInfo.Instance.begin();
      this.renderer.clear();
      this.renderer.render(this.scene, this.camera);
      DebugInfo.Instance.end();
    };

    animate();
  }

  onresize() {
    console.log('onresive');
    if (this.renderer) {
      this.renderer.setDrawingBufferSize(
        this.element.clientWidth,
        this.element.clientHeight,
        1
      );
      this.updateCamera();
    }
  }

  onmousewheel(event: WheelEvent) {
    const scale = event.deltaY > 0 ? 1.5 : 0.8;
    this.zoom = this.zoom * scale;
    if (this.zoom < 0.01) {
      this.zoom = 0.01;
    } else if (this.zoom > 800) {
      this.zoom = 800;
    }
    this.updateCamera();

    const point = this.getScreenPos2WorldPos(event.offsetX, event.offsetY);
    if (point) {
      this.followLabel.innerText = `(${point.x}, ${point.y})`;
    }
    this.dispatchEvent({
      type: WinEvent.onmousewheel,
      event,
      worldPosition: point,
    });
  }

  onmousemove(event: MouseEvent) {
    // console.log(event);
    // console.log(event.clientX, event.clientY);
    this.followLabel.style.top = (event.clientY + 10).toString();
    this.followLabel.style.left = (event.clientX + 10).toString();
    const point = this.getScreenPos2WorldPos(event.offsetX, event.offsetY);
    if (point) {
      this.followLabel.innerText = `(${point.x}, ${point.y})`;
    }

    if (this.state === State.CameraMove || event.button === 2) {
      const width = this.camera.right - this.camera.left;
      const dx = (width / this.clientWidth) * event.movementX;
      const height = this.camera.top - this.camera.bottom;
      const dy = (height / this.clientHeight) * event.movementY;
      this.camera.position.x -= dx;
      this.camera.position.y += dy;
    }

    this.dispatchEvent({
      type: WinEvent.onmousemove,
      event,
      worldPosition: point,
    });
  }

  public onkeydown(e: KeyboardEvent): any | void {
    if (e.key === ' ') {
      // this.resetCamera();
    }
  }

  public onkeyup(e: KeyboardEvent): any | void {
    if (e.key === ' ') {
      this.resetCamera();
    }
  }
  public onmousedown(event: MouseEvent): any | void {
    if (event.button === 2) {
      this.state = State.CameraMove;
    }

    const point = this.getScreenPos2WorldPos(event.offsetX, event.offsetY);
    this.dispatchEvent({
      type: WinEvent.onmousedown,
      event,
      worldPosition: point,
    });
  }

  public onmouseup(e: MouseEvent): any | void {
    if (e.button === 2) {
      this.state = State.None;
    }
  }

  private resetCamera() {
    this.camera.position.set(0, 0, 100);
    this.zoom = 1;
    this.updateCamera();
  }

  private updateCamera() {
    const aspect = this.clientHeight / this.clientWidth;
    const halfWidth = this.viewHalfWidth * this.zoom;
    this.camera.left = -halfWidth;
    this.camera.right = halfWidth;
    this.camera.top = halfWidth * aspect;
    this.camera.bottom = -halfWidth * aspect;
    this.camera.updateProjectionMatrix();

    const unitScale = (halfWidth * 2) / this.clientWidth; // 每个像素宽
    this.zoomUnitText.innerText = (unitScale * 40).toPrecision(5).toString();
  }

  public setFollowLabelText(text: string) {
    if (this.followLabel) {
      text = text.trim();
      this.followLabel.innerText = text;
      if (text.length <= 0) {
        this.followLabel.style.display = 'none';
      }
    }
  }

  public destory() {
    this.isRunning = false;
    if (this.element !== null) {
      const content = document.getElementById('content');
      if (content && content.children[0] == this.element) {
        content.removeChild(this.element);
      }
      this.element.onresize = null;
      this.element = null;
    }
    if (this.drawController) {
      this.drawController.destory();
      this._drawController = null;
    }
  }

  public adaptPositonNumber(num: number): number {
    let fixedBase = 1;
    if (this.zoom < 0.05) {
      fixedBase = 100;
    } else if (this.zoom < 0.3) {
      fixedBase = 10;
    }
    return Math.round(num * fixedBase) / fixedBase;
  }

  public getScreenPos2WorldPos(x: number, y: number): Vector2 {
    this.rayCast.setFromCamera(
      {
        x: (x / this.clientWidth) * 2 - 1,
        y: -(y / this.clientHeight) * 2 + 1,
      },
      this.camera
    );
    const intersect = this.rayCast.intersectObject(this.plane);
    if (intersect && intersect.length > 0) {
      const pos = intersect[0].point;
      return new Vector2(
        this.adaptPositonNumber(pos.x),
        this.adaptPositonNumber(pos.y)
      );
    }
    return null;
  }
}

export default new ThreeApp();
